#include "msg.h"
#include <string.h>
#include <stdlib.h>

Byte * uint32ToBytesBigEndian(uint32_t data);
Byte * uint32sToBytesBigEndian(uint32_t *data, int dataLen); 
int bytesToIntBigEndian(Byte* src, int offset);
uint32_t bytesToUint32BigEndian(Byte* src, int offset);

Msg msgCreate(Byte *topic, Byte *payload, int32_t type, uint32_t clientId, uint32_t expireAfterNow)
{
    Msg msg;
    msg.header.version = 1;
    msg.header.type = type;
    msg.header.expireAfterNow = expireAfterNow;
    msg.header.clientId = clientId;

    msg.body.topic = topic;
    msg.body.payload = payload;
    msg.body.topicLen = strlen(topic);
    msg.body.payloadLen = strlen(payload);

    msg.header.bodyLen = msg.body.topicLen + msg.body.payloadLen + 8;
    return msg;
}
int msgLength(Msg msg)
{
    return 28 + msg.body.topicLen + msg.body.payloadLen;
}

Byte * msgToBytes(Msg msg)
{
    uint32_t uints[7] = {msg.header.version,
     msg.header.type, 
     msg.header.bodyLen, 
     msg.header.expireAfterNow, 
     msg.header.clientId,
     msg.body.topicLen,
     msg.body.payloadLen};

    Byte* ret = uint32sToBytesBigEndian(uints, 7);

    int retLen = 7 * 4;
    int topicPayloadLen = msg.body.topicLen + msg.body.payloadLen;
    Byte* result = malloc(retLen + topicPayloadLen);
    for (int i = 0; i < retLen; i++)
    {
        result[i] = ret[i];
    }
    for (int i = 0; i < msg.body.topicLen; i++) {
        result[i + retLen] = msg.body.topic[i];
    }
    for (int i = 0; i < msg.body.payloadLen; i++) {
        result[i + retLen + msg.body.topicLen] = msg.body.payload[i];
    }
    free(ret);
    return result;
}

Byte* bytesCopy(Byte *src, int start, int end)
{
    int len = end - start;
    Byte* ret = malloc(len + 1);
    for (int i = 0; i < len; i++)
    {
        ret[i] = src[start + i];
    }
    ret[len] = '\0';
    return ret;
}

Msg msgBytesToMsg(Byte* src)
{
    Msg msg;
    // msg.header.version = bytesToUint32BigEndian(src, 0);
    // msg.header.type = bytesToIntBigEndian(src, 4);
    // msg.header.bodyLen = bytesToUint32BigEndian(src, 8);
    // msg.header.expireAfterNow = bytesToUint32BigEndian(src, 12);
    // msg.header.clientId = bytesToUint32BigEndian(src, 16);

    msg.header = msgBytesToMsgHeader(src);

    msg.body.topicLen = bytesToUint32BigEndian(src, 20);
    msg.body.payloadLen = bytesToUint32BigEndian(src, 24);
    uint32_t topicEnd = 28 + msg.body.topicLen;
    msg.body.topic = bytesCopy(src, 28, topicEnd);
    msg.body.payload = bytesCopy(src, topicEnd, topicEnd + msg.body.payloadLen);

    return msg;
}

int msgHeaderLen()
{
    return sizeof(MsgHeader);
}

MsgHeader msgBytesToMsgHeader(Byte* src)
{
    MsgHeader header;
    header.version = bytesToUint32BigEndian(src, 0);
    header.type = bytesToIntBigEndian(src, 4);
    header.bodyLen = bytesToUint32BigEndian(src, 8);
    header.expireAfterNow = bytesToUint32BigEndian(src, 12);
    header.clientId = bytesToUint32BigEndian(src, 16);
    return header;
}

Msg msgBodyBytesToMsg(Byte* bodyBytes, MsgHeader header)
{
    Msg msg;
    msg.header = header;

    MsgBody body;
    body.topicLen = bytesToUint32BigEndian(bodyBytes, 0);
    body.payloadLen = bytesToUint32BigEndian(bodyBytes, 4);
    uint32_t topicEnd = 8 + body.topicLen;
    body.topic = bytesCopy(bodyBytes, 8, topicEnd);
    body.payload = bytesCopy(bodyBytes, topicEnd, topicEnd + body.payloadLen);
    msg.body=body;
    return msg;
}


int bytesToIntBigEndian(Byte* src, int offset) 
{
    int value;
    value = (int) (((src[offset] & 0xFF)<< 24)
                   | ((src[offset+1] & 0xFF)<< 16)
            | ((src[offset+2] & 0xFF)<< 8)
            | (src[offset+3] & 0xFF));
    return value;
}

uint32_t bytesToUint32BigEndian(Byte* src, int offset) 
{
    uint32_t value;
    value = (uint32_t) (((src[offset] & 0xFF)<< 24)
                   | ((src[offset + 1] & 0xFF)<< 16)
            | ((src[offset + 2] & 0xFF)<< 8)
            | (src[offset + 3] & 0xFF));
    return value;
}

Byte * uint32ToBytesBigEndian(uint32_t data) 
{
    Byte * bytes = malloc(4);
    bytes[0] = data >> 24;
    bytes[1] = data >> 16;
    bytes[2] = data >> 8;
    bytes[3] = data >> 0;
    return bytes;
}

Byte * uint32sToBytesBigEndian(uint32_t *data, int dataLen) 
{
    Byte * bytes = malloc(4 * dataLen);
    for(int i = 0; i < dataLen; i++)
    {
        bytes[0 + i*4] = data[i] >> 24;
        bytes[1 + i*4] = data[i] >> 16;
        bytes[2 + i*4] = data[i] >> 8;
        bytes[3 + i*4] = data[i] >> 0;
    }
    return bytes;
}